import {
  Injectable,
  Logger,
  NotFoundException,
  BadRequestException,
  ConflictException,
  InternalServerErrorException,
} from '@nestjs/common';
import { DeviceAuditCodeRepository } from './device-audit-code.repository';
import { DevicesService } from '../devices/devices.service';
import { AuditStatus } from '../common/enums';
import { DeviceMetaRequestDto } from '../devices/dto/device-meta.dto';
import { v4 as uuidv4 } from 'uuid';
import { createObjectCsvStringifier } from 'csv-writer';

@Injectable()
export class DeviceAuditService {
  private readonly logger = new Logger(DeviceAuditService.name);

  constructor(
    private readonly deviceAuditCodeRepository: DeviceAuditCodeRepository,
    private readonly devicesService: DevicesService,
  ) {}

  /**
   * Generate a new audit code
   * @param adminId Admin user ID who generates the code
   * @returns The generated audit code and verify code
   */
  async generateAuditCode(adminId: string) {
    const results = await this.generateAuditCodesBatch(adminId, 1);
    return results[0];
  }

  /**
   * Generate multiple audit codes in batch
   * @param adminId Admin user ID who generates the codes
   * @param count Number of codes to generate (max 100)
   * @returns Array of generated audit codes and verify codes
   */
  async generateAuditCodesBatch(adminId: string, count: number) {
    // Limit the count to 100 to prevent excessive database load
    const limitedCount = Math.min(count, 100);

    // Generate all codes in memory first
    const codesData = [];
    const results = [];

    for (let i = 0; i < limitedCount; i++) {
      // Generate UUIDs and slice to create shorter codes
      const auditUuid = uuidv4().replace(/-/g, '').toUpperCase().substring(0, 8);
      const verifyUuid = uuidv4().replace(/-/g, '').toUpperCase().substring(0, 8);

      // Add prefixes to distinguish between audit number and verify code
      const auditNumber = `A-${auditUuid}`;
      const verifyCode = `V-${verifyUuid}`;

      codesData.push({
        auditNumber,
        verifyCode,
        createdBy: adminId,
      });

      results.push({
        auditNumber,
        verifyCode,
      });
    }

    try {
      // Bulk create all codes in a single transaction
      const savedCodes = await this.deviceAuditCodeRepository.createBatchInTransaction(codesData);

      // Match the saved codes with the generated codes
      for (let i = 0; i < savedCodes.length; i++) {
        results[i].auditNumber = savedCodes[i].auditNumber;
        results[i].verifyCode = savedCodes[i].verifyCode;
      }

      if (count === 1) {
        this.logger.log(
          `Admin ${adminId} generated new audit code: ${results[0].auditNumber} (${results[0].auditNumber.length} chars) / ${results[0].verifyCode} (${results[0].verifyCode.length} chars)`,
        );
      } else {
        this.logger.log(`Admin ${adminId} generated ${limitedCount} audit codes in batch`);
      }

      return results;
    } catch (error) {
      this.logger.error(`Failed to generate audit codes: ${error.message}`);
      throw new InternalServerErrorException('Failed to generate audit codes');
    }
  }

  /**
   * Verify audit code and create a new device
   * @param auditNumber Audit number
   * @param verifyCode Verify code
   * @param deviceSerialNumber Device serial number
   * @param deviceMeta Device metadata
   */
  async verifyAndCreateDevice(
    auditNumber: string,
    verifyCode: string,
    deviceSerialNumber: string,
    deviceMeta: DeviceMetaRequestDto,
  ) {
    // Find the audit code
    const auditCode = await this.deviceAuditCodeRepository.findByAuditNumberAndVerifyCode(
      auditNumber,
      verifyCode,
    );

    if (!auditCode) {
      this.logger.warn(`Invalid audit code: ${auditNumber} / ${verifyCode}`);
      throw new NotFoundException('Invalid audit number or verify code');
    }

    // Check if the audit code is already used
    if (auditCode.usedAt) {
      this.logger.warn(`Audit code already used: ${auditNumber}`);
      throw new ConflictException('This audit code has already been used');
    }

    // Check if device with this serial number already exists
    const existingDevice = await this.devicesService.findBySerialNumber(deviceSerialNumber);
    if (existingDevice) {
      this.logger.warn(`Device already exists: ${deviceSerialNumber}`);
      throw new ConflictException('Device with this serial number already exists');
    }

    // Create a new device
    try {
      await this.devicesService.registerDevice(
        {
          serialNumber: deviceSerialNumber,
          metadata: deviceMeta,
        },
        auditCode.createdBy, // Use the admin who created the audit code
      );
      this.logger.log(`Created new device: ${deviceSerialNumber}`);
    } catch (error) {
      this.logger.error(`Failed to create device: ${error.message}`);
      throw new BadRequestException('Failed to create device');
    }

    // Mark the audit code as used
    await this.deviceAuditCodeRepository.markAsUsed(auditNumber, deviceSerialNumber);
    this.logger.log(`Activated device ${deviceSerialNumber} with audit code ${auditNumber}`);

    return { status: AuditStatus.PASS };
  }

  /**
   * Get audit status for a device
   * @param deviceSerialNumber Device serial number
   */
  async getAuditStatus(deviceSerialNumber: string) {
    // Check if the device exists
    const device = await this.devicesService.findBySerialNumber(deviceSerialNumber);
    if (!device) {
      this.logger.warn(`Device not found when checking audit status: ${deviceSerialNumber}`);
      throw new NotFoundException('Device not found');
    }

    const status = await this.deviceAuditCodeRepository.getAuditStatus(deviceSerialNumber);
    this.logger.log(`Audit status for device ${deviceSerialNumber}: ${status}`);

    return { status };
  }

  /**
   * Export all audit codes to CSV format
   * @returns CSV string containing all audit codes
   */
  async exportAuditCodesToCSV(): Promise<string> {
    // Get all audit codes including soft deleted ones, limited to 100,000 records
    const auditCodes = await this.deviceAuditCodeRepository.findAllIncludingDeleted(100000);

    // Define CSV header with Chinese titles
    const csvStringifier = createObjectCsvStringifier({
      header: [
        { id: 'auditNumber', title: '审核编号' },
        { id: 'verifyCode', title: '校验码' },
        { id: 'createdAt', title: '创建时间' },
        { id: 'usedAt', title: '使用时间' },
        { id: 'deviceSerialNumber', title: '设备号' },
        { id: 'createdBy', title: '创建者ID' },
        { id: 'isDeleted', title: '是否删除' },
      ],
    });

    // Format data for CSV
    const records = auditCodes.map((code) => ({
      auditNumber: code.auditNumber,
      verifyCode: code.verifyCode,
      createdAt: code.createdAt ? code.createdAt.toISOString() : '',
      usedAt: code.usedAt ? code.usedAt.toISOString() : '',
      deviceSerialNumber: code.deviceSerialNumber || '',
      createdBy: code.createdBy,
      isDeleted: code.isDeleted ? '是' : '否',
    }));

    this.logger.log(`Exporting ${records.length} audit codes to CSV`);

    // Generate CSV
    return csvStringifier.getHeaderString() + csvStringifier.stringifyRecords(records);
  }
}
